package com.panopset.swing;

import static com.panopset.Util.log;

import java.awt.Font;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.border.TitledBorder;

/**
 * Provide set of small, medium, and large fonts.
 * 
 * @author Karl Dinwiddie
 */
public final class FontManager {

	public static String LUCIDIA = "Lucida Sans Regular";
	public static String LUCIDIA_BOLD = "Lucida Sans Bold";
	public static String LUCIDIA_OBLIQUE = "Lucida Sans Oblique";
	public static String LUCIDIA_BOLD_OB = "Lucida Sans Bold Oblique";

	public static String L_BRIGHT = "Lucida Bright Regular";
	public static String L_BRIGHT_BOLD = "Lucida Bright Bold";
	public static String L_BRIGHT_OBLIQUE = "Lucida Bright Italic";
	public static String L_BRIGHT_BOLD_OB = "Lucida Bright Bold Italic";

	public static String MONOSPACE = "Lucida Sans Typewriter Regular";
	public static String MONOSPACE_BOLD = "Lucida Sans Typewriter Bold";
	public static String MONOSPACE_OBLIQUE = "Lucida Sans Typewriter Oblique";
	public static String MONOSPACE_BOLD_OB = "Lucida Sans Typewriter Bold Oblique";

	/**
	 * Arial.
	 */
	public static final String ARIAL = "Arial";
	/**
	 * Dialog.
	 */
	public static final String DIALOG = "Dialog";
	/**
	 * DocumentManager key.
	 */
	public static final String KEY = "com.panopset.fontsize";

	private Font monospace;
	private Font monospace_bold;
	private Font monospace_oblique;
	private Font monospace_bold_ob;

	private Font lucidia;
	private Font lucidia_bold;
	private Font lucidia_oblique;
	private Font lucidia_bold_ob;

	private Font l_bright;
	private Font l_bright_bold;
	private Font l_bright_oblique;
	private Font l_bright_bold_ob;
	
	private Font boldArial;
	private Font plainArial;
	private Font borderTitle;

	/**
	 * @param size
	 *            Size.
	 */
	public void setSize(final Size size) {
		if (size == null) {
			return;
		}
		monospace = new Font(MONOSPACE, Font.PLAIN, size.getValue());
		monospace_bold = new Font(MONOSPACE, Font.BOLD, size.getValue());
		monospace_bold_ob = new Font(MONOSPACE, Font.ITALIC, size.getValue());
		boldArial = new Font(ARIAL, Font.BOLD, size.getValue());
		plainArial = new Font(ARIAL, Font.PLAIN, size.getValue());
		int dip = 2;
		if (FontManager.Size.SMALL.val == size.val) {
			dip = 1;
		}
		borderTitle = new Font(ARIAL, Font.ITALIC, size.getValue() - dip);
		updateAll();
		for (final FontManager.Size s : FontManager.Size.values()) {
			JMenuItem mi = s.getMenuItem();
			if (s == size) {
				mi.setSelected(true);
			} else {
				mi.setSelected(false);
			}
		}
	}

	/**
	 * @param sizeString
	 *            New size.
	 */
	public void setSize(final String sizeString) {
		setSize(Size.find(com.panopset.Util.parseInt(sizeString)));
	}

	/**
	 * @param size
	 *            Size.
	 */
	public static void setDefaultSize(final Size size) {
		defaultSize = size;
	}

	/**
	 * Default size.
	 */
	private static Size defaultSize = Size.SMALL;

	/**
	 * @return Monospace font.
	 */
	public static Font getMonospace() {
		return getInstance().monospace;
	}

	/**
	 * @return Bold Arial font.
	 */
	public static Font getBoldArial() {
		return getInstance().boldArial;
	}

	/**
	 * @return Plain Arial font.
	 */
	public static Font getPlainArial() {
		return getInstance().plainArial;
	}

	/**
	 * @return Plain Arial font.
	 */
	public static Font getBorderTitle() {
		return getInstance().borderTitle;
	}

	/**
	 * Font classifications.
	 */
	public enum FontType {

		/**
		 * Plain Arial.
		 */
		plainArial,
		/**
		 * Bold Arial.
		 */
		boldArial,
		/**
		 * Monospace.
		 */
		monospace,
		/**
		 * Border title.
		 */
		borderTitle
	}

	/**
	 * SMALL, MEDIUM, LARGE.
	 */
	public static enum Size {

		/**
		 * Small size is 9.
		 */
		SMALL(9),
		/**
		 * Medium size is 12.
		 */
		MEDIUM(12),
		/**
		 * Large size is 16.
		 */
		LARGE(16),
		/**
		 * Extra large size is 24.
		 */
		SUPER(24),
		/**
		 * Cinema size is 48.
		 */
		CINEMA(48);
		/**
		 * Value.
		 */
		private final int val;

		/**
		 * @param fontSize
		 *            Font size.
		 */
		Size(final int fontSize) {
			val = fontSize;
		}

		/**
		 * Find size.
		 * 
		 * @param fontSize
		 *            Required font size.
		 * @return Next smallest size if no match found.
		 */
		public static Size find(final int fontSize) {
			for (Size size : Size.values()) {
				if (size.getValue() == fontSize) {
					return size;
				}
			}
			for (Size size : Size.values()) {
				if (size.getValue() > fontSize) {
					return size;
				}
			}
			return defaultSize;
		}

		/**
		 * @return Font size.
		 */
		public int getValue() {
			return val;
		}

		private JMenuItem mi;

		public JMenuItem getMenuItem() {
			if (mi == null) {
				mi = new JCheckBoxMenuItem(name());
			}
			return mi;
		}
	}

	/**
	 * @param component
	 *            to register.
	 * @param fontType
	 *            Font type.
	 */
	private void registerComponent(final JComponent component,
			final FontType fontType) {
		if (component == null || fontType == null) {
			throw new RuntimeException();
		}
		getCmps().put(component, fontType);
		component.setFont(getFontForType(fontType));
	}

	/**
	 * @param component
	 *            Component.
	 * @param fontType
	 *            Font type.
	 */
	public static void register(final JComponent component,
			final FontType fontType) {
		getInstance().registerComponent(component, fontType);
	}

	/**
	 * @param border
	 *            Titled border.
	 * @param fontType
	 *            Font type.
	 */
	public static void register(final TitledBorder border,
			final FontType fontType) {
		getInstance().registerTitleBorder(border, fontType);
	}

	/**
	 * @param border
	 *            to register.
	 * @param fontType
	 *            Font type.
	 */
	private void registerTitleBorder(final TitledBorder border,
			final FontType fontType) {
		if (border == null || fontType == null) {
			throw new RuntimeException();
		}
		getBrdrs().put(border, fontType);
		border.setTitleFont(getFontForType(fontType));
	}

	/**
	 * @param fontType
	 *            Font type.
	 * @return Font.
	 */
	public static Font getFont(final FontType fontType) {
		return getInstance().getFontForType(fontType);
	}

	/**
	 * @param fontType
	 *            Font type.
	 * @return Font.
	 */
	private Font getFontForType(final FontType fontType) {
		try {
			Object rtn = FontManager.class.getMethod(
					"get"
							+ com.panopset.Strings
									.upperCaseFirstLetter(fontType.name()),
					new Class<?>[0]).invoke(this, new Object[0]);
			if (rtn instanceof Font) {
				Font f = (Font) rtn;
				return f;
			}
			throw new NullPointerException();
		} catch (Exception ex) {
			log(ex);
			throw new RuntimeException(ex);
		}
	}

	/**
	 * Update all registered fonts.
	 */
	public synchronized void updateAll() {
		for (Entry<JComponent, FontType> e : getCmps().entrySet()) {
			e.getKey().setFont(getFontForType(e.getValue()));
			e.getKey().invalidate();
		}
		for (Entry<TitledBorder, FontType> e : getBrdrs().entrySet()) {
			e.getKey().setTitleFont(getFontForType(e.getValue()));
		}
		if (App.app != null) {
			App.app.f.getFrame().validate();
		}
	}

	/**
	 * @return Registered components.
	 */
	private Map<JComponent, FontType> getCmps() {
		if (cmps == null) {
			cmps = new HashMap<JComponent, FontType>();
		}
		return cmps;
	}

	/**
	 * @return Registered borders.
	 */
	private Map<TitledBorder, FontType> getBrdrs() {
		if (brdrs == null) {
			brdrs = new HashMap<TitledBorder, FontType>();
		}
		return brdrs;
	}

	/**
	 * Components.
	 */
	private static Map<JComponent, FontType> cmps;
	/**
	 * Borders.
	 */
	private static Map<TitledBorder, FontType> brdrs;

	/**
	 * Start with default size.
	 */
	private FontManager() {
		setSize(defaultSize);
	}

	/**
	 * Based on SingletonHolder inner class by Bill Pugh.
	 * 
	 * 
	 * <h5>References</h5>
	 * <ul>
	 * <li>
	 * <a href="http://en.wikipedia.org/wiki/Singleton_pattern">
	 * http://en.wikipedia.org/wiki/Singleton_pattern </a></li>
	 * </ul>
	 * 
	 */
	private static final class Singleton {

		/**
		 * Instance variable.
		 */
		private static final FontManager INSTANCE = new FontManager();

		/**
		 * Private constructor.
		 */
		private Singleton() {
			// Prevent instantiation.
		}
	}

	/**
	 * @return static Random instance.
	 */
	public static FontManager getInstance() {
		return Singleton.INSTANCE;
	}
}
